﻿#pragma once

#include "geom/geom_point.h"
#include "topo_base.h"

namespace CAGD
{

/**
 * 属性基类.
 */
class PropertyBase
{
  public:
    /**
     * @brief 基本构造.
     * @param name
     * @see
     */
    PropertyBase(std::string name) : m_name(name)
    {
    }

    /**
     * @brief 返回属性名.
     * @return
     * @see
     */
    std::string Name() const
    {
        return m_name;
    }

  protected:
    std::string m_name;
};

/**
 * 属性模板.
 */
template <typename T, TopoType Type> class PropertyHandle : public PropertyBase
{
  public:
    /**
     * @brief 基本构造.
     * @see
     */
    PropertyHandle(std::string name) : PropertyBase(name), m_t(T())
    {
    }

    /**
     * @brief 返回属性值.
     * @return
     * @see
     */
    T &Property()
    {
        return m_t;
    }

  protected:
    T m_t;
};

// 特例化为针对不同类型的属性
template <typename T> using VPropertyHandle = PropertyHandle<T, TopoType::VERTEX>;
template <typename T> using EPropertyHandle = PropertyHandle<T, TopoType::EDGE>;
template <typename T> using CPropertyHandle = PropertyHandle<T, TopoType::COEDGE>;
template <typename T> using FPropertyHandle = PropertyHandle<T, TopoType::FACE>;

//////////////////////////////////////////////////////////////////////////////////////////
///
///

/**
 * 网格类型.
 */
enum class MeshType
{
    TRI,  // 3 边
    QUAD, // 4 边
    PENT, // 5 边
    MIX   // 混合
};

/**
 * 材质类型.
 */
struct Material
{
    bool enable;   // 是否启用
    BVec diffuse;  // 漫反射
    BVec specular; // 镜面反射
};

/**
 * 定义类型.
 */
using VertexHandle = TopoVertex *;
using EdgeHandle = TopoEdge *;
using CoedgeHandle = TopoCoedge *;
using FaceHandle = TopoFace *;

/**
 * 半边网格类型.
 */
class TopoMesh : public TopoBase
{
  public:
    /**
     * @brief 增加一个属性.
     * @param handle
     * @see
     */
    template <typename T, TopoType Type> void AddProperty(PropertyHandle<T, Type> handle)
    {
        auto name = handle.Name();
        switch (Type)
        {
        case TopoType::FACE: {
            m_faceProperties[name] = std::map<FaceHandle, PropertyBase *>();
            auto &m = m_faceProperties[name];
            for (auto &f : m_faces)
                m[f] = new PropertyHandle<T, Type>(name);
            break;
        }
        case TopoType::COEDGE: {
            m_coedgeProperties[name] = std::map<CoedgeHandle, PropertyBase *>();
            auto &m = m_coedgeProperties[name];
            for (auto &c : m_coedges)
                m[c] = new PropertyHandle<T, Type>(name);
            break;
        }
        case TopoType::EDGE: {
            m_edgeProperties[name] = std::map<EdgeHandle, PropertyBase *>();
            auto &m = m_edgeProperties[name];
            for (auto &e : m_edges)
                m[e] = new PropertyHandle<T, Type>(name);
            break;
        }
        case TopoType::VERTEX: {
            m_vertexProperties[name] = std::map<VertexHandle, PropertyBase *>();
            auto &m = m_vertexProperties[name];
            for (auto &v : m_vertices)
                m[v] = new PropertyHandle<T, Type>(name);
            break;
        }
        default:
            break;
        }
    }

    /**
     * @brief 通过顶点和属性名获得属性值.
     * @return
     * @param handle
     * @param vertex
     * @see
     */
    template <typename T> T &Property(VPropertyHandle<T> handle, VertexHandle vertex)
    {
        auto name = handle.Name();
        auto pit = m_vertexProperties.find(name);
        assert(pit != m_vertexProperties.end());

        // 如果没有属性就创建属性
        VPropertyHandle<T> *prop = nullptr;
        auto vit = pit->second.find(vertex);
        if (vit == pit->second.end())
        {
            prop = new VPropertyHandle<T>(name);
            pit->second[vertex] = prop;
        }
        else
            prop = static_cast<VPropertyHandle<T> *>(vit->second);

        // 因为创建的时候已经初始化了，所以一定能找到
        return prop->Property();
    }

    /**
     * @brief 通过边和属性名获得属性值.
     * @return
     * @param handle
     * @param edge
     * @see
     */
    template <typename T> T &Property(EPropertyHandle<T> handle, EdgeHandle edge)
    {
        auto name = handle.Name();
        auto pit = m_edgeProperties.find(name);
        assert(pit != m_edgeProperties.end());

        // 如果没有属性就创建属性
        EPropertyHandle<T> *prop = nullptr;
        auto eit = pit->second.find(edge);
        if (eit == pit->second.end())
        {
            prop = new EPropertyHandle<T>(name);
            pit->second[edge] = prop;
        }
        else
            prop = static_cast<EPropertyHandle<T> *>(eit->second);

        // 因为创建的时候已经初始化了，所以一定能找到
        return prop->Property();
    }

    /**
     * @brief 通过半边和属性名获得属性值.
     * @return
     * @param handle
     * @param coedge
     * @see
     */
    template <typename T> T &Property(CPropertyHandle<T> handle, CoedgeHandle coedge)
    {
        auto name = handle.Name();
        auto pit = m_coedgeProperties.find(name);
        assert(pit != m_coedgeProperties.end());

        // 如果没有属性就创建属性
        CPropertyHandle<T> *prop = nullptr;
        auto cit = pit->second.find(coedge);
        if (cit == pit->second.end())
        {
            prop = new CPropertyHandle<T>(name);
            pit->second[coedge] = prop;
        }
        else
            prop = static_cast<CPropertyHandle<T> *>(cit->second);

        // 因为创建的时候已经初始化了，所以一定能找到
        return prop->Property();
    }

    /**
     * @brief 通过面和属性名获得属性值.
     * @return
     * @param handle
     * @param face
     * @see
     */
    template <typename T> T &Property(FPropertyHandle<T> handle, FaceHandle face)
    {
        auto name = handle.Name();
        auto pit = m_faceProperties.find(name);
        assert(pit != m_faceProperties.end());

        // 如果没有属性就创建属性
        FPropertyHandle<T> *prop = nullptr;
        auto fit = pit->second.find(face);
        if (fit == pit->second.end())
        {
            prop = new FPropertyHandle<T>(name);
            pit->second[face] = prop;
        }
        else
            prop = static_cast<FPropertyHandle<T> *>(fit->second);

        // 因为创建的时候已经初始化了，所以一定能找到
        return prop->Property();
    }

    /**
     * @brief 移除属性.
     * @param handle
     * @see
     */
    template <typename T, TopoType Type> void RemoveProperty(PropertyHandle<T, Type> handle)
    {
        auto name = handle.Name();
        switch (Type)
        {
        case TopoType::FACE: {
            auto it = m_faceProperties.find(name);
            auto &m = it->second;
            for (auto &p : m)
                delete p.second;

            m_faceProperties.erase(it);
            break;
        }
        case TopoType::COEDGE: {
            auto it = m_coedgeProperties.find(name);
            auto &m = it->second;
            for (auto &p : m)
                delete p.second;

            m_coedgeProperties.erase(it);
            break;
        }
        case TopoType::EDGE: {
            auto it = m_edgeProperties.find(name);
            auto &m = it->second;
            for (auto &p : m)
                delete p.second;

            m_edgeProperties.erase(it);
            break;
        }
        case TopoType::VERTEX: {
            auto it = m_vertexProperties.find(name);
            auto &m = it->second;
            for (auto &p : m)
                delete p.second;

            m_vertexProperties.erase(it);
            break;
        }
        default:
            break;
        }
    }

    /**
     * @brief 检查是否具有指定属性.
     * @return
     * @param name
     * @see
     */
    template <typename T, TopoType Type> bool HasProperty(PropertyHandle<T, Type> handle) const
    {
        auto name = handle.Name();
        switch (Type)
        {
        case TopoType::FACE: {
            if (m_faceProperties.find(name) != m_faceProperties.end())
                return true;
            break;
        }
        case TopoType::COEDGE: {
            if (m_coedgeProperties.find(name) != m_coedgeProperties.end())
                return true;
            break;
        }
        case TopoType::EDGE: {
            if (m_edgeProperties.find(name) != m_edgeProperties.end())
                return true;
            break;
        }
        case TopoType::VERTEX: {
            if (m_vertexProperties.find(name) != m_vertexProperties.end())
                return true;
            break;
        }
        default:
            break;
        }
        return false;
    }

  public:
    /**
     * @brief 构造类型.
     * @param type
     * @param name
     * @see
     */
    TopoMesh(MeshType type, std::string name);

    /**
     * @brief 析构.
     * @see
     */
    ~TopoMesh();

    /**
     * @brief 拓扑类型.
     * @return
     * @see
     */
    TopoType Type() const override;

    /**
     * @brief 获得名称.
     * @return
     * @see
     */
    std::string Name() const;

    /**
     * @brief 获得顶点数.
     * @return
     * @see
     */
    int VertexNum() const;

    /**
     * @brief 获得面数.
     * @return
     * @see
     */
    int FaceNum() const;

    /**
     * @brief 获得半边数.
     * @return
     * @see
     */
    int CoedgeNum() const;

    /**
     * @brief 获得边数.
     * @return
     * @see
     */
    int EdgeNum() const;

    /**
     * @brief 顶点.
     * @return
     * @see
     */
    std::unordered_set<VertexHandle> &Vertices();

    /**
     * @brief 边.
     * @return
     * @see
     */
    std::unordered_set<EdgeHandle> &Edges();

    /**
     * @brief coedge.
     * @return
     * @see
     */
    std::unordered_set<CoedgeHandle> &Coedges();

    /**
     * @brief 面.
     * @return
     * @see
     */
    std::unordered_set<FaceHandle> &Faces();

    /**
     * @brief 计算面法向.
     * @see
     */
    void RequireFaceNormals();

    /**
     * @brief 移除面法向.
     * @see
     */
    void RemoveFaceNormals();

    /**
     * @brief 计算顶点法向 Gouraud 插值.
     * @see
     */
    void RequireVertexNormals();

    /**
     * @brief 移除顶点法向.
     * @see
     */
    void RemoveVertexNormals();

    /**
     * @brief 创建顶点纹理坐标.
     * @see
     */
    void RequireVertexTexcoords();

    /**
     * @brief 移除顶点纹理坐标.
     * @see
     */
    void RemoveVertexTexcoords();

    /**
     * @brief 创建顶点，并返回指针（可选检查顶点是否重合，如果重合，返回重合顶点）.
     * @return
     * @param p
     * @param testSame
     * @see
     */
    VertexHandle CreateVertex(Handle(GeomPoint) p, bool testSame = false);

    /**
     * @brief 创建边，返回与输入顶点顺序同向的 coedge.
     * @return
     * @param p
     * @param testSame
     * @see
     */
    CoedgeHandle CreateEdge(VertexHandle v1, VertexHandle v2, bool testSame = false);

    /**
     * @brief 创建面并返回（如果传入顶点数大于网格类型规定的数量，会自动划分为多个面），默认自动判定衔接相邻面.
     * @return
     * @param es
     * @see
     */
    std::vector<FaceHandle> CreateFace(const std::vector<CoedgeHandle> &es);

    /**
     * @brief 通过顶点直接创建面.
     * @return
     * @param vs
     * @see
     */
    std::vector<FaceHandle> CreateFace(const std::vector<VertexHandle> &vs);

    /**
     * @brief 删除顶点和相邻的面及其半边.
     * @return
     * @param v
     * @see
     */
    bool DeleteVertex(VertexHandle v);

    /**
     * @brief 删除边和相邻的面.
     * @return
     * @param e
     * @see
     */
    bool DeleteEdge(EdgeHandle e);

    /**
     * @brief 删除面和对应的半边（假定面上只有一个 loop）.
     * @return
     * @param f
     * @see
     */
    bool DeleteFace(FaceHandle f);

    /**
     * @brief 删除孤立顶点.
     * @return
     * @param type
     * @see
     */
    bool DeleteIsolated();

    /**
     * @brief 获得起始点.
     * @return
     * @param co
     * @see
     */
    VertexHandle Origin(CoedgeHandle co) const;

    /**
     * @brief 获得目标点.
     * @return
     * @param co
     * @see
     */
    VertexHandle Destination(CoedgeHandle co) const;

    /**
     * @brief 获得一个面上的所有顶点（不包括其它 loop）（保顺序，逆时针）.
     * @return
     * @param f
     * @see
     */
    std::vector<VertexHandle> Vertices(FaceHandle f) const;

    /**
     * @brief 获得一个面上的所有边（不包括其它 loop）（保顺序，逆时针）.
     * @return
     * @param f
     * @see
     */
    std::vector<EdgeHandle> Edges(FaceHandle f) const;

    /**
     * @brief 获取两个面的公共边.
     * @return
     * @param f0
     * @param f1
     * @see
     */
    std::vector<EdgeHandle> CommonEdges(FaceHandle f0, FaceHandle f1) const;

    /**
     * @brief 获得 v 相邻的所有顶点（不保顺序）.
     * @return
     * @param v
     * @see
     */
    std::unordered_set<VertexHandle> AdjacentVertices(VertexHandle v) const;

    /**
     * @brief 获得 v 相邻的所有面（不保顺序）.
     * @return
     * @param v
     * @see
     */
    std::unordered_set<FaceHandle> AdjacentFaces(VertexHandle v) const;

    /**
     * @brief 获得 f 相邻的所有面（不保顺序）.
     * @return
     * @param f
     * @see
     */
    std::unordered_set<FaceHandle> AdjacentFaces(FaceHandle f) const;

    /**
     * @brief 转换为可渲染的网格.
     * @return
     * @see
     */
    Handle(TriangleMesh) ToTriangleMesh();

  public:
    Material m_material;

  protected:
    std::string m_name;
    MeshType m_type;

    std::unordered_set<VertexHandle> m_vertices;
    std::unordered_set<EdgeHandle> m_edges;
    std::unordered_set<CoedgeHandle> m_coedges;
    std::unordered_set<FaceHandle> m_faces;

    std::map<std::string, std::map<VertexHandle, PropertyBase *>> m_vertexProperties;
    std::map<std::string, std::map<EdgeHandle, PropertyBase *>> m_edgeProperties;
    std::map<std::string, std::map<CoedgeHandle, PropertyBase *>> m_coedgeProperties;
    std::map<std::string, std::map<FaceHandle, PropertyBase *>> m_faceProperties;
};

/**
 * 读取 obj 网格.
 */
Handle(TopoMesh) load_from_obj(const std::string &filename, float scale = 1.0f);

/**
 * 读取 dat 网格.
 */
Handle(TopoMesh) load_from_dat(const std::string &filename, float scale = 1.0f);

/**
 * 保存 obj 网格.
 */
void save_as_dat(Handle(TopoMesh) mesh, const std::string &filename);

}; // namespace CAGD
